#include "mcu_device.hpp"

#include <log/log.hpp>

#include <commons/decoder/len_field_based_frame_decoder.hpp>
#include <commons/net/tcp_client.hpp>
#include <commons/sync_async/async_timer.hpp>

namespace sros::device
{
    McuDevice::~McuDevice()
    {
    }

    RetBool McuDevice::init()
    {
        // 加载配置
        auto ret = load_config();
        if (!ret.v)
        {
            return ret;
        }

        // 初始化设备状态
        init_dev_status_();

        // 初始化回调接口列表
        init_cb_list_();

        // 初始化TCP Client
        ret = init_tcp_client_();

        // 初始化定期器，用于做周期任务
        timer_ = std::make_shared<async::Timer>();

        return ret;
    }

    void McuDevice::run()
    {
        if (client_)
        {
            client_->run();
        }

        if (timer_)
        {
            timer_->safe_start(10, 100, 0, [this](std::size_t times) { on_timer_trigger_(times); });
        }
    }

    void McuDevice::close()
    {
        if (timer_)
        {
            timer_->safe_stop();
            timer_->safe_clear();
            timer_ = nullptr;
        }

        if (client_)
        {
            client_->close();
            client_ = nullptr;
        }
        
        if (config_)
        {
            config_->deinit();
            config_ = nullptr;
        }
        
        {
            std::lock_guard<std::mutex> lck(cans_status_mtx_);
            cans_status_.clear();
        }
        
        {
            std::lock_guard<std::mutex> lck(uarts_status_mtx_);
            uarts_status_.clear();
        }

        {
            std::lock_guard<std::mutex> lck(power_status_mtx_);
            power_status_.clear();
        }
    }

    RetBool McuDevice::send_can_msg(const proto::ReqCanMsg &msg)
    {
        if (!client_ || client_->status() != type::Status::RUNNING)
        {
            return {false, "client is not running"};
        }
        auto data = proto::ins().pck_trans(msg);
        auto size = client_->write_some(data);
        if (size != data.size())
        {
            return {false, "write_some size is not equal data.size()"};
        }
        else
        {
            return {true};
        }
    }

    RetBool McuDevice::send_uart_msg(const proto::ReqUartMsg &msg)
    {
        if (!client_ || client_->status() != type::Status::RUNNING)
        {
            return {false, "client is not running"};
        }
        auto data = proto::ins().pck_trans(msg);
        auto size = client_->write_some(data);
        if (size != data.size())
        {
            return {false, "write_some size is not equal data.size()"};
        }
        else
        {
            return {true};
        }
    }

    RetBool McuDevice::power_ctl(const uint8_t &id, const bool &is_on)
    {
        if (!client_ || client_->status() != type::Status::RUNNING)
        {
            return {false, "client is not running"};
        }

        if  (id <= proto::PowerType::Power_Min || id >= proto::PowerType::Power_Max)
        {
            return {false, "power id is not range [Power_Min+1, PowerMax-1]"};
        }

        auto data = proto::ins().pck_power(id, is_on ? 1 : 0);
        auto size = client_->write_some(data);
        if (size != data.size())
        {
            return {false, "write_some size is not equal data.size()"};
        }
        else
        {
            return {true};
        }
    }

    RetBool McuDevice::load_config()
    {
        if (config_)
        {
            return {true};
        }

        std::string root = util::env_var("HOME");
        if (root.empty())
        {
            root = ".";
        }
        std::string config_path = util::format("{}/.sros/{}/{}/config/mcu_config.json", root, MODULE, NODE);

        config_ = std::make_shared<commons::Config<cfg::McuCfg>>();
        auto ret = config_->init(config_path);
        if (!ret.v)
        {
            return ret;
        }
 
        return config_->load();
    }

    RetBool McuDevice::init_tcp_client_()
    {
        if (config_ == nullptr)
        {
            return {false, "config is nullptr"};
        }

        cfg::McuCfg cfg_;
        auto ret = config_->get(cfg_);
        if (!ret.v)
        {
            return ret;
        }

        net::TcpClientParam param;
        param.remote_ip = cfg_.remote_ip;
        param.remote_port = cfg_.remote_port;
        param.max_retry_conn_times = cfg_.max_retry_conn_times;
        param.retry_conn_enable = cfg_.retry_conn_enable;
        param.conn_timeout_mill = cfg_.conn_timeout_mill;
        param.decoder = std::make_shared<decoder::LenFieldBasedFrameDecoder>(decoder::ByteOrder::BIG_ENDIAN_, 3, 2, 5, 3);

        param.on_msg_arrival = [this](const net::TcpClientRecvMsg &&msg)
        {
            auto ret = proto::ins().unpack(msg.buff);
            if (!ret.v)
            {
                LOG_WARN("tcp on_msg_arrival: {} {}", ret.v, ret.msg);
            }
        };

        param.on_error = [this](const int &&ec, const std::string &&msg)
        {
            LOG_ERROR("tcp on_error: {} {}", ec, msg);
        };

        param.on_status_changed = [this](const int &&s1, const std::string &&m1, const int &&s2, const std::string &&m2)
        {
            LOG_INFO("tcp on_status: [{},{}]->[{},{}]", s1, m1, s2, m2);
        };

        client_ = std::make_shared<net::TcpClient>(param);

        return {true};
    }

    void McuDevice::on_timer_trigger_(std::size_t times)
    {
        if (!client_ || client_->status() != type::Status::RUNNING)
        {
            return;
        }

        static std::vector<uint8_t> buffer = proto::ins().pck_heartbeat();
        client_->async_write_some(buffer);

        if (config_ == nullptr)
        {
            return;
        }

        // 每 1000ms 检测一次
        if (times % 10 != 0)
        {
            return;
        }
        
        cfg::McuCfg cfg_;
        auto ret = config_->get(cfg_);
        if (!ret.v)
        {
            return;
        }

        {
            std::lock_guard<std::mutex> lck(cans_status_mtx_);
            for (const auto &it : cfg_.cans)
            {
                if (cans_status_.find(it.index) == cans_status_.end() || cans_status_.at(it.index).active == 1)
                {
                    continue;
                }

                proto::CanConfig can_cfg {
                    .type = proto::ConfigType::CAN,
                    .index = it.index,
                    .prescaler = it.prescaler,
                    .seg1 = it.seg1,
                    .seg2 = it.seg2
                };

                std::vector<uint8_t> buffer = proto::ins().pck_init_board(can_cfg);
                client_->async_write_some(buffer);
            }
        }

        {
            std::lock_guard<std::mutex> lck(uarts_status_mtx_);
            for (const auto &it : cfg_.uarts)
            {
                if (uarts_status_.find(it.index) == uarts_status_.end() || uarts_status_.at(it.index).active == 1)
                {
                    continue;
                }
 
                proto::UartConfig uart_cfg {
                    .type = proto::ConfigType::UART,
                    .index = it.index,
                    .bitrate = it.bitrate,
                    .wordlength = it.wordlength,
                    .stopbit = it.stopbits,
                    .parity = (hal::ParityType)it.parity
                };

                std::vector<uint8_t> buffer = proto::ins().pck_init_board(uart_cfg);
                client_->async_write_some(buffer);
            }
        }
    }

    void McuDevice::init_cb_list_()
    {
        proto::ins().install_init_cb([this](const proto::RspInitMsg &msg)
        {
            switch (msg.type)
            {
            case proto::ConfigType::CAN:
            {
                std::lock_guard<std::mutex> lck(cans_status_mtx_);
                if (cans_status_.find(msg.index) == cans_status_.end())
                {
                    return;
                }

                cans_status_.at(msg.index).msg = msg.msg;
            }
            break;
            case proto::ConfigType::UART:
            {
                std::lock_guard<std::mutex> lck(uarts_status_mtx_);
                if (uarts_status_.find(msg.index) == uarts_status_.end())
                {
                    return;
                }
                
                uarts_status_.at(msg.index).msg = msg.msg;
            }
            break;
            default:
            {
                LOG_WARN("not handle init response.");
            }
            break;
            }
        });

        proto::ins().install_version_cb([this](const proto::RspVersionMsg &msg)
        {
            if (version_msg_cb_)
            {
                version_msg_cb_(msg);
            }
        });

        proto::ins().install_io_cb([this](const proto::RspIoMsg &msg)
        {
            if (io_msg_cb_)
            {
                io_msg_cb_(msg);
            }
        });

        proto::ins().install_uart_cb([this](const proto::RspUartMsg &msg)
        {
            if (uart_msg_cb_)
            {
                uart_msg_cb_(msg);
            }
        });

        proto::ins().install_can_cb([this](const proto::RspCanMsg &msg)
        {
            if (can_msg_cb_)
            {
                can_msg_cb_(msg);
            }
        });

        proto::ins().install_uart_state([this](const proto::RspUartState &sts)
        {
            proto::UartStatus status;
            {
                std::lock_guard<std::mutex> lck(uarts_status_mtx_);
                if (uarts_status_.find(sts.index) == uarts_status_.end())
                {
                    return;
                }
                
                uarts_status_.at(sts.index).active = sts.active;
                uarts_status_.at(sts.index).error = sts.error;
                uarts_status_.at(sts.index).state = sts.state;

                status = uarts_status_.at(sts.index);
            }
            
            if (uart_status_cb_)
            {
                uart_status_cb_(status);
            }
        });

        proto::ins().install_can_state([this](const proto::RspCanState &sts)
        {
            proto::CanStatus status;
            {
                std::lock_guard<std::mutex> lck(cans_status_mtx_);
                if (cans_status_.find(sts.index) == cans_status_.end())
                {
                    return;
                }
                
                cans_status_.at(sts.index).active = sts.active;
                cans_status_.at(sts.index).error = sts.error;
                cans_status_.at(sts.index).state = sts.state;            
                
                status = cans_status_.at(sts.index);
            }

            if (can_status_cb_)
            {
                can_status_cb_(status);
            }
        });

        proto::ins().install_power_state([this](const std::map<uint32_t, bool> &sts)
        {
            {
                std::lock_guard<std::mutex> lck(power_status_mtx_);
                power_status_ = sts;
            }

            if (power_status_cb_)
            {
                power_status_cb_(sts);
            }
        });

        proto::ins().install_encoder_cb([this](const std::map<uint32_t, int32_t> &msg)
        {
            if (encoder_cb_)
            {
                encoder_cb_(msg);
            }
        });
    }

    void McuDevice::init_dev_status_()
    {
        if (config_ == nullptr)
        {
            return;
        }

        cfg::McuCfg cfg_;
        auto ret = config_->get(cfg_);
        if (!ret.v)
        {
            return;
        }

        {
            std::lock_guard<std::mutex> lck(cans_status_mtx_);
            cans_status_.clear();
            for (auto &it : cfg_.cans)
            {
                proto::CanStatus status {
                    .active = 0,
                    .index = it.index,
                    .error = 0,
                    .state = 0,
                    .msg = std::string()
                };
                cans_status_.insert({it.index, status});
            }
        }

        {
            std::lock_guard<std::mutex> lck(uarts_status_mtx_);
            uarts_status_.clear();
            for (auto &it : cfg_.uarts)
            {
                proto::UartStatus status {
                    .active = 0,
                    .index = it.index,
                    .error = 0,
                    .state = 0,
                    .msg = std::string()
                };
                uarts_status_.insert({it.index, status});
            }
        }
    }
}